
//
// 101020 - AH - Created.
// 101022a- AH - Made an AttributeObject, need to be if to be returned as 
//               output by the NetworkSpoke.
// 101022b- AH - matchKeywords(Set<String>).
// 101022c- AH - size().
// 101111 - AH - hasNode(String), hasNodes(String...) and getNode(String).
// 101116 - AH - (Bien) Now have explicit reference to sub graph.
// 101117 - AH - (Hector) getLinkageBetweenCluster().
// 110519 - AH - (Garage) Lookup on node names now is not case sensitive.
// 110606 - AH - (Garage) toStringSoekratNames().
//

package org.soekrat.engine.network;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jgrapht.Graph;
import org.jgrapht.graph.DirectedSubgraph;
import org.soekrat.AttributeObject;
import org.soekrat.engine.SoekratName;
import org.soekrat.engine.SoekratNameMatcher;
import org.soekrat.engine.SoekratNameMatcherSingleton;

/**
 * <p>
 *   Cluster in a
 *   {@link Network}.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC) 
 */
public final class Cluster extends AttributeObject 
  implements Iterable<NetworkNode> 
{
  private final Network m_owner;
  
  private final DirectedSubgraph<NetworkNode, NetworkEdge> m_graph;
  
  private Map<String, NetworkNode> m_nodes=new HashMap<String, NetworkNode>();
  
  private void addIndexedNode(NetworkNode node)
  {
    String name=node.getName();
    String norm=name.toLowerCase();
    m_nodes.put(norm, node);
  }
  
  /**
   * <p>
   *   Package scope constructor since created by
   *   {@link Network} only.
   * </p>
   * @param owner {@code Network} {@code this} is in.
   * @param sc    Strongly connected component of {@code this} cluster's nodes.
   */
  Cluster(Network owner, DirectedSubgraph<NetworkNode, NetworkEdge> sc)
  {
    m_owner=owner;
    m_graph=sc;
    
    for (NetworkNode node : sc.vertexSet()) 
    {
      //m_nodes.put(node.getName(), node);
      addIndexedNode(node);
    }
  }
  
  /**
   * <p>
   *   Gets owner of {@code this} cluster.
   * </p>
   * @return Network that has {@code this} cluster.
   */
  public Network getNetwork(){ return m_owner; }
  
  /**
   * <p>
   *   Tells if has named node.
   * </p>
   * @param name Node's name.
   * @return {@code true} if has, {@code false} if has not.
   */
  public boolean hasNode(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    String norm=name.toLowerCase();
    return m_nodes.containsKey(norm);
  }
  
  /**
   * <p>
   *   Tells if contains all named node.
   * </p>
   * @param names Names.
   * @return {@code false} if one or more names not found else returns 
   *         {@code true}.
   * @throws IllegalArgumentException If {@code names} is the empty array.
   */
  public boolean hasNodes(String... names)
  {
    if (names==null)
    {
      throw new NullPointerException("names");
    }
    
    int n=names.length;
    
    if (n==0)
    {
      throw new IllegalArgumentException("names is the empty array");
    }
    
    for (int i=0; i<n; i++)
    {
      String name=names[i];
      if (name==null)
      {
        throw new NullPointerException("names["+i+"]");
      }
      
      String norm=name.toLowerCase();
      if (!m_nodes.containsKey(norm)) return false;
    }
    
    return true;
  }
  
  /**
   * <p>
   *   Gets named
   *   {@link NetworkNode}.
   * </p>
   * @param name Name on node to get.
   * @return {@code NetworkNode} or {@code null} if not found.
   */
  public NetworkNode getNode(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    SoekratNameMatcher matcher=SoekratNameMatcherSingleton.INSTANCE;
    
    for (NetworkNode node : m_nodes.values())
    {
      SoekratName sname=node.getSoekratName();
      if (matcher.match(name, sname)) return node;
    }
    return null;
    
    //String norm=name.toLowerCase();
    //return m_nodet.get(norm);
  }
  
  /**
   * <p>
   *   Gets nodes of cluster.
   * </p>
   * @return Nodes.
   */
  public NetworkNode[] getNodes()
  { 
    return m_nodes.values().toArray(NetworkNode.E_ARRAY);
  }
  
  /**
   * <p>
   *   Gets the number of nodes in this cluster.
   * </p>
   * @return Count.
   */
  public int size(){ return m_nodes.size(); }
  
  /**
   * <p>
   *   Gets the number of links in cluster.
   * </p>
   * @return Count.
   */
  public int getNumberLinks(){ return m_graph.edgeSet().size(); }
  
  /**
   * <p>
   *   Finds which keywords are matched by node names in {@code this}
   *   {@code Cluster}.
   * </p>
   * @param keywords Keywords.
   * @return Matched keywords.
   */
  public Set<String> matchKeywords(Set<String> keywords)
  {
    if (keywords==null)
    {
      throw new NullPointerException("names");
    }
    
    Set<String> matches=new HashSet<String>();
    
    for (NetworkNode node : this) 
    {
      String name=node.getName();
      if (keywords.contains(name)) matches.add(name);  
    }
    
    return matches;
  }
  
  // Specified in Iterable<NetworkNode>.
  public Iterator<NetworkNode> iterator(){ return m_nodes.values().iterator(); }

  @Override
  public String toString() 
  {
    StringBuilder sb=new StringBuilder();
    sb.append("{");
    boolean first=true;
    for (NetworkNode node : this) 
    {
      if (!first) sb.append(", ");
      first=false;
      sb.append(node.getName());
    }
    sb.append("}");
    return sb.toString();
  }
  
  /**
   * <p>
   *   As
   *   {@link #toString()} but nodes
   *   {@link SoekratName}s are written.
   * </p>
   * @return {@link String} presentation.
   */
  public String toStringSoekratNames() 
  {
    StringBuilder sb=new StringBuilder();
    sb.append("{");
    boolean first=true;
    for (NetworkNode node : this) 
    {
      if (!first) sb.append(", ");
      first=false;
      sb.append(node.getSoekratName());
    }
    sb.append("}");
    return sb.toString();
  }
  
  /**
   * <p>
   *   Computes the linkage between {@code this} and the other clusters. 
   * </p>
   * <p>
   *   Linkage for {@code this} cluster is the number of links from matched
   *   nodes to nodes in another cluster that are also matched.
   * </p>
   * @return Linkage.
   */
  public int getLinkageBetweenCluster()
  {
    int retVal=0;
    
    Graph<NetworkNode, NetworkEdge> networkGraph=m_owner.getGraph();
    
    for (NetworkNode node : m_nodes.values())
    {
      if (node.getBoolean("key"))
      {
        Set<NetworkEdge> edges=networkGraph.edgesOf(node);
        
        for (NetworkEdge edge : edges) 
        {
          NetworkNode from=edge.from();
          NetworkNode to=edge.to();
        
          if (from==node)
          {
            if (to.getBoolean("key"))
            {
              if (!m_graph.containsVertex(to)) retVal++;
            }
          }
        }
      }
    }
    
    return retVal;
  }
  
  /**
   * <p>
   *   The empty array shared.
   * </p>
   */
  public static final Cluster[] E_ARRAY=new Cluster[0];
  
}
